// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2020, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * Support raph init for lombo platforms
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include "lombo_init.h"
#include "pvrsrv_device.h"
#include "rgxdevice.h"

static struct lombo_context *lbcontext;

static int RgxLomboClockEnable(void)
{
	int ret = PVRSRV_OK;

	/* enable parent clk */
	ret = clk_prepare_enable(lbcontext->parent_clk);
	if (ret) {
		PVR_DPF((PVR_DBG_ERROR, "%s:parent_clk enable fail", __func__));
		goto exit;
	}

	/* enable gpu clk */
	ret = clk_prepare_enable(lbcontext->bus_gating);
	if (ret) {
		PVR_DPF((PVR_DBG_ERROR, "%s:bus gating enable fail", __func__));
		goto exit;
	}
	ret = clk_prepare_enable(lbcontext->bus_reset);
	if (ret) {
		PVR_DPF((PVR_DBG_ERROR, "%s: bus reset enable fail", __func__));
		goto exit;
	}
	ret = clk_prepare_enable(lbcontext->axi_clk);
	if (ret) {
		PVR_DPF((PVR_DBG_ERROR, "%s: axi clk enable fail", __func__));
		goto exit;
	}

	clk_set_rate(lbcontext->core_clk, lbcontext->clk_rate);
	ret = clk_prepare_enable(lbcontext->core_clk);
	if (ret) {
		PVR_DPF((PVR_DBG_ERROR, "%s: core clk enable fail", __func__));
		goto exit;
	}

	/* power domain on */
	writel(0x40000000, lbcontext->topbase + 0x10);
	/* enable clk gating */
	writel(0x40000007, lbcontext->topbase + 0x10);
	/* core release */
	writel(0xC0000007, lbcontext->topbase + 0x10);
	/* unmask irq */
	writel(0x1, lbcontext->topbase + 0x1c);
exit:
	return ret;
}

static int RgxLomboClockDisable(void)
{
	int ret = PVRSRV_OK;

	/* mask irq */
	writel(0x0, lbcontext->topbase + 0x1c);
	/* core reset */
	writel(0x40000007, lbcontext->topbase + 0x10);
	/* disable clk gating */
	writel(0x40000000, lbcontext->topbase + 0x10);
	/* power domain off */
	writel(0x00000000, lbcontext->topbase + 0x10);

	/* disable gpu clk */
	clk_disable(lbcontext->core_clk);

	return ret;
}

PVRSRV_ERROR RgxLomboPrePowerState(IMG_HANDLE hSysData,
				   PVRSRV_DEV_POWER_STATE eNewPowerState,
				   PVRSRV_DEV_POWER_STATE eCurrentPowerState,
				   IMG_BOOL bForced)
{
	int ret = PVRSRV_OK;

	if (eNewPowerState == PVRSRV_DEV_POWER_STATE_ON) {
		ret = RgxLomboClockEnable();
		if (ret)
			goto exit;
	}

exit:
	return ret;
}

PVRSRV_ERROR RgxLomboPostPowerState(IMG_HANDLE hSysData,
				    PVRSRV_DEV_POWER_STATE eNewPowerState,
				    PVRSRV_DEV_POWER_STATE eCurrentPowerState,
				    IMG_BOOL bForced)
{
	int ret = PVRSRV_OK;

	if (eNewPowerState == PVRSRV_DEV_POWER_STATE_OFF)
		ret = RgxLomboClockDisable();

	return ret;
}

IMG_HANDLE RgxLomboInit(PVRSRV_DEVICE_CONFIG *psDevConfig)
{
	struct device *dev = (struct device *)psDevConfig->pvOSDevice;
	struct platform_device *pdev = to_platform_device((struct device *)dev);
	struct resource *res = NULL;

	lbcontext = devm_kzalloc(dev, sizeof(struct lombo_context), GFP_KERNEL);
	if (lbcontext == NULL) {
		PVR_DPF((PVR_DBG_ERROR,
			"%s: Failed to kzalloc context", __func__));
		goto err_exit;
	}

	if (!dev->dma_mask)
		dev->dma_mask = &dev->coherent_dma_mask;

	lbcontext->dev_config = psDevConfig;

	lbcontext->bus_gating = of_clk_get(dev->of_node, 0);
	if (IS_ERR(lbcontext->bus_gating)) {
		PVR_DPF((PVR_DBG_ERROR,
			"%s: of_clk_get(0) bus_gating failed", __func__));
		goto err_clk;
	}
	lbcontext->bus_reset = of_clk_get(dev->of_node, 1);
	if (IS_ERR(lbcontext->bus_reset)) {
		PVR_DPF((PVR_DBG_ERROR,
			"%s: of_clk_get(1) bus_reset failed", __func__));
		goto err_clk;
	}
	lbcontext->axi_clk = of_clk_get(dev->of_node, 2);
	if (IS_ERR(lbcontext->axi_clk)) {
		PVR_DPF((PVR_DBG_ERROR,
			"%s: of_clk_get(2) axi_clk failed", __func__));
		goto err_clk;
	}
	lbcontext->core_clk = of_clk_get(dev->of_node, 3);
	if (IS_ERR(lbcontext->core_clk)) {
		PVR_DPF((PVR_DBG_ERROR,
			"%s: of_clk_get(3) core_clk failed", __func__));
		goto err_clk;
	}
	lbcontext->parent_clk = of_clk_get(dev->of_node, 4);
	if (IS_ERR(lbcontext->parent_clk)) {
		PVR_DPF((PVR_DBG_ERROR,
			"%s: of_clk_get(4) parent_clk failed", __func__));
		goto err_clk;
	}

	if (of_property_read_u32(dev->of_node, "clk_rate",
				 &lbcontext->clk_rate))
		PVR_DPF((PVR_DBG_ERROR, "%s: get clk rate failed", __func__));

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res) {
		lbcontext->topbase = devm_ioremap_resource(&pdev->dev, res);
		if (IS_ERR(lbcontext->topbase)) {
			PVR_DPF((PVR_DBG_ERROR,
			"%s: devm_ioremap_resource failed for top", __func__));
			goto err_res1;
		}
		lbcontext->topstart = res->start;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	if (res) {
		lbcontext->membase = devm_ioremap_resource(&pdev->dev, res);
		if (IS_ERR(lbcontext->membase)) {
			PVR_DPF((PVR_DBG_ERROR,
			"%s: devm_ioremap_resource failed for mem", __func__));
			goto err_res2;
		}
		lbcontext->memstart = res->start;
	}

	RgxLomboPrePowerState(lbcontext,
				     PVRSRV_DEV_POWER_STATE_ON,
				     PVRSRV_DEV_POWER_STATE_DEFAULT,
				     IMG_FALSE);

	return (IMG_HANDLE)lbcontext;
/* err_res3: */
	devm_iounmap(&pdev->dev, lbcontext->membase);
	lbcontext->membase = NULL;
err_res2:
	devm_iounmap(&pdev->dev, lbcontext->topbase);
	lbcontext->topbase = NULL;
err_res1:
	clk_put(lbcontext->core_clk);
	lbcontext->core_clk = NULL;
err_clk:
	if (lbcontext->core_clk) {
		clk_put(lbcontext->core_clk);
		lbcontext->core_clk = NULL;
	}
	if (lbcontext->axi_clk) {
		clk_put(lbcontext->axi_clk);
		lbcontext->axi_clk = NULL;
	}
	if (lbcontext->bus_reset) {
		clk_put(lbcontext->bus_reset);
		lbcontext->bus_reset = NULL;
	}
	if (lbcontext->bus_gating) {
		clk_put(lbcontext->bus_gating);
		lbcontext->bus_gating = NULL;
	}

	devm_kfree(dev, lbcontext);
	lbcontext = NULL;
err_exit:
	return NULL;
}

void RgxLomboDeinit(IMG_HANDLE hSysData)
{
	struct device *dev = (struct device *)lbcontext->dev_config->pvOSDevice;

	devm_iounmap(dev, lbcontext->membase);
	devm_iounmap(dev, lbcontext->topbase);
	lbcontext->membase = NULL;
	lbcontext->topbase = NULL;

	if (lbcontext->core_clk) {
		clk_put(lbcontext->core_clk);
		lbcontext->core_clk = NULL;
	}
	if (lbcontext->axi_clk) {
		clk_put(lbcontext->axi_clk);
		lbcontext->axi_clk = NULL;
	}
	if (lbcontext->bus_reset) {
		clk_put(lbcontext->bus_reset);
		lbcontext->bus_reset = NULL;
	}
	if (lbcontext->bus_gating) {
		clk_put(lbcontext->bus_gating);
		lbcontext->bus_gating = NULL;
	}
	devm_kfree(dev, lbcontext);
	lbcontext = NULL;
}

